﻿@page "/localization"

<h3>本地化</h3>

<p>
    本地化是为给定语言和地区定制应用程序的过程。<code>BootstrapBlazor</code> 组件允许您将其 UI 元素转换为所需的语言。这包括按钮、过滤器操作符属性等文本。组件内部默认使用当前请求 <code>UI</code> 文化语言，本文将向您展示如何在应用程序中使用此功能：
</p>

<Tips>
    阅读以下知识点前请先查看 <a href="https://docs.microsoft.com/zh-cn/aspnet/core/blazor/globalization-localization" target="_blank">微软官方文档</a>
    <div>由于 <code>wasm</code> 模式无法获取系统语言文化信息，默认文化信息为 <code>en</code>，<Anchor Target="lang">请查阅文章最后更改默认语言章节</Anchor></div>
</Tips>

<p><b>本地化在组件中的工作原理</b></p>

<p>
    <div>
        <code>BootstrapBlazor</code> 组件额外支持使用 <code>Json</code> 类型的键值信息作为资源文件，将其解析为 <code>UI</code> 中呈现的字符串。<a href="https://www.nuget.org/packages/BootstrapBlazor/" target="_blank">BootstrapBalzor</a> 包自带以下资源文件。
    </div>
    <ul class="ul-demo mt-2">
        <li>中文（zh）</li>
        <li>英语（en）</li>
        <li>德语（de）</li>
        <li>葡萄牙语（pu）</li>
    </ul>
    <div class="mt-2">
        组件内置本地化语言回退机制，如请求文化为 <code>zh-CN</code> 时，如未提供相对应的文化资源文件时，内置逻辑通过父级文化进行尝试本地化，以 <code>zh-CN</code> 为例回退机制如下：<code>zh-CN</code> 到 <code>zh</code>
    </div>
    <div class="mt-2">
        如果设置的本地化语言未提供资源文件回落后仍无法找到资源文件后，使用 <code>FallbackCulture</code> 参数设置的文化信息进行本地化，默认为 <code>en</code>
    </div>

    <div class=mt-2><b>特别注意：</b></div>

    <div class="mt-2">由于某些系统如 <code>Centos</code> <code>Ubuntu</code> <code>mac</code> 等程序运行后线程无法获得文化信息，可以通过配置文件设置默认文化信息：</div>
</p>

<Pre>{
  "BootstrapBlazorOptions": {
    "DefaultCultureInfo": "en"
  }
}</Pre>

<p><b>开启本地化功能</b></p>

<p><b>Server-Side App</b></p>

<p><b>1. 配置文件</b></p>

<p>通过 <code>FallbackCultureName</code> 设置回退文化信息，即未找到当前请求的文化信息时使用此配置文化，通过 <code>SupportedCultures</code> 设置支持的文化集合</p>

<Pre>{
  "BootstrapBlazorOptions": {
    "FallbackCultureName": "en",
    "SupportedCultures": [
      "zh-CN",
      "en-US"
    ]
  }
}</Pre>

<p><b>2. 启用 .NET 核心本地化服务</b></p>
<Pre>public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 增加 BootstrapBlazor 组件
        services.AddBootstrapBlazor();

        // 增加本地化服务配置信息
        services.AddRequestLocalization&lt;IOptions&lt;BootstrapBlazorOptions&gt;&gt;((localizerOption, blazorOption) =>
        {
            var supportedCultures = blazorOption.Value.GetSupportedCultures();

            localizerOption.SupportedCultures = supportedCultures;
            localizerOption.SupportedUICultures = supportedCultures;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 启用本地化中间件并设置支持的文化信息
        app.UseRequestLocalization(app.ApplicationServices.GetService&lt;IOptions&lt;RequestLocalizationOptions&gt;&gt;()!.Value);
    }
}</Pre>

<p><b>3. 实现 UI 本地化信息存储（例如，cookie）</b></p>
<Pre>[Route("[controller]/[action]")]
public class CultureController : Controller
{
    public IActionResult SetCulture(string culture, string redirectUri)
    {
        if (!string.IsNullOrEmpty(culture))
        {
            HttpContext.Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture, culture)));
        }

        return LocalRedirect(redirectUri);
    }

    public IActionResult ResetCulture(string redirectUri)
    {
        HttpContext.Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);

        return LocalRedirect(redirectUri);
    }
}</Pre>

<p><b>3. 添加允许用户更改本地化的 UI</b></p>
<Pre>@@inherits BootstrapComponentBase
@@inject IOptions&lt;BootstrapBlazorOptions&gt; BootstrapOptions
@@inject NavigationManager NavigationManager
@@inject ICultureStorage CultureStorage

&lt;div @@attributes="@@AdditionalAttributes" class="@@ClassString"&gt;
    &lt;label&gt;请选择语言：&lt;/label&gt;
    &lt;Select Value="@@SelectedCulture" OnSelectedItemChanged="@@SetCulture"&gt;
        &lt;Options&gt;
            @@foreach (var kv in Configuration.GetSupportCultures())
            {
                &lt;SelectOption Text="@@kv.Key" Value="@@kv.Value" /&gt;
            }
        &lt;/Options&gt;
    &lt;/Select&gt;
&lt;/div&gt;

@@code {
    private string? ClassString =&gt; CssBuilder.Default("culture-selector")
        .AddClassFromAttributes(AdditionalAttributes)
        .Build();

    private string SelectedCulture { get; set; } = CultureInfo.CurrentUICulture.Name;

    private async Task SetCulture(SelectedItem item)
    {
        if (CultureStorage.Mode == CultureStorageMode.Webapi)
        {
            // 使用 api 方式 适用于 Server-Side 模式
            if (SelectedCulture != item.Value)
            {
                var culture = item.Value;
                var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
                var query = $"?culture={Uri.EscapeDataString(culture)}&redirectUri={Uri.EscapeDataString(uri)}";

                // use a path that matches your culture redirect controller from the previous steps
                NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
            }
        }
        else
        {
            var cultureName = item.Value;
            if (cultureName != CultureInfo.CurrentCulture.Name)
            {
                await JSRuntime.InvokeAsync&lt;string&gt;(identifier: "$.blazorCulture.set", cultureName);
                var culture = new CultureInfo(cultureName);
                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;

                NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
            }
        }
    }
}</Pre>

<p><b>4. 附加 <code>Json</code> 资源文件</b></p>

<p>
    <code>BootstrapBlazor</code> 组件库即支持微软默认的 <code>resx</code> 格式资源，也支持嵌入式 <code>json</code> 格式资源，以及特定物理 <code>json</code> 文件
</p>

<p>
    <div>资源文件为合并关系，寻址规则优先级为</div>

    <ul class="ul-demo mt-3">
        <li>微软 resx 嵌入式资源文件</li>
        <li>外置 json 物理文件</li>
        <li>json 嵌入式资源文件</li>
        <li>内置 json 资源文件</li>
    </ul>
</p>

<Pre>public void ConfigureServices(IServiceCollection services)
{
    services.AddBootstrapBlazor(localizationAction: options =>
    {
        // 设置 RESX 格式多语言资源文件 如 Program.{CultureName}.resx
        options.ResourceManagerStringLocalizerType = typeof(Program);

        // 设置 Json 格式嵌入式资源文件
        options.AdditionalAssemblies = new[] { typeof(BootstrapBlazor.Shared.App).Assembly };

        // 设置 Json 物理路径文件
        options.AdditionalJsonFiles = new string[]
        {
            @@"D:\Argo\src\BootstrapBlazor\src\BootstrapBlazor.Server\Locales\zh-TW.json",
            @@"D:\Argo\src\BootstrapBlazor\src\BootstrapBlazor.Server\Locales\zh-CN.json"
        };
    });
}
</Pre>

<p>或者使用服务扩展方法 <code>ConfigureJsonLocalizationOptions</code></p>

<Pre>public void ConfigureServices(IServiceCollection services)
{
    services.AddBootstrapBlazor();
    services.ConfigureJsonLocalizationOptions(op =>
    {
        // 附加自己的 json 多语言文化资源文件 如 zh-TW.json
        op.AdditionalJsonAssemblies = new Assembly[]
        {
            typeof(BootstrapBlazor.Shared.App).Assembly,
        };

        // 设置 Json 物理路径文件
        options.AdditionalJsonFiles = new string[]
        {
            @@"D:\Argo\src\BootstrapBlazor\src\BootstrapBlazor.Server\Locales\zh-TW.json",
            @@"D:\Argo\src\BootstrapBlazor\src\BootstrapBlazor.Server\Locales\zh-CN.json"
        };
    });
}</Pre>

<p><b>Web Assembly</b></p>

<p><b>1. 启用 .NET 核心本地化服务</b></p>
<Pre>public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);

    builder.RootComponents.Add&lt;App&gt;("app");

    builder.Services.AddTransient(sp =&gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

    // 增加 BootstrapBlazor 组件
    builder.Services.AddBootstrapBlazor();

    builder.Services.AddSingleton&lt;ICultureStorage, DefaultCultureStorage&gt;();

    // 增加本地化
    builder.Services.Configure&lt;BootstrapBlazorOptions&gt;(op =>
    {
        op.ToastDelay = 4000;
        op.SupportedCultures = new List&lt;string&gt; { "zh-CN", "en-US" };
    });

    var host = builder.Build();

    await GetCultureAsync(host);

    await host.RunAsync();
}

private static async Task GetCultureAsync(WebAssemblyHost host)
{
    var jsRuntime = host.Services.GetRequiredService&lt;IJSRuntime&gt;();
    var cultureName = await jsRuntime.InvokeAsync&lt;string&gt;("$.blazorCulture.get") ?? "zh-CN";
    var culture = new CultureInfo(cultureName);
    CultureInfo.DefaultThreadCurrentCulture = culture;
    CultureInfo.DefaultThreadCurrentUICulture = culture;
}

internal class DefaultCultureStorage : ICultureStorage
{
    public CultureStorageMode Mode { get; set; } = CultureStorageMode.LocalStorage;
}
</Pre>

<p><b>2. 实现 UI 本地化信息存储（例如，localStorage）</b></p>

<p>与 Server-Side 一致</p>

<p id="lang"><b>更改默认语言设置</b></p>

<Pre>public static async Task Main(string[] args)
{
    // 设置默认文化为 zh-CN
    CultureInfo.CurrentCulture = new CultureInfo("zh-CN");
    CultureInfo.CurrentUICulture = new CultureInfo("zh-CN");

    // ...

    var host = builder.Build();

    await GetCultureAsync(host);

    await host.RunAsync();
}
</Pre>

<Video Name="localizer" />
